package neuronalenetzeelearning.model;

import java.util.HashMap;
import java.util.Vector;
import neuronalenetzeelearning.exceptions.DimensionException;
import neuronalenetzeelearning.view.IController;

// <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
// #[regen=yes,id=DCE.75E8576D-13E6-22F2-E9E5-08DF3F4FC0CA]
// </editor-fold>
public class Graph {

    private IController controller;
    private Vector<Double> eingabe;

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.9871012E-391A-540A-71FD-2F2E57EDB92C]
    // </editor-fold>
    /**
     * Gewichtsmatrix
     */
//    private Matrix matrix;
    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.6C3E1E45-54EB-A572-9D2A-AC3B04488D18]
    // </editor-fold>
    /**
     * Vektor mit den Kanten
     */
    private Vector<Edge> edges;
    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.C90413AF-8ABC-1058-788C-5509DDC8BEE5]
    // </editor-fold>
    /**
     * Vektor mit den thresholds eizelner Knoten
     * Die Reihenfolge der Komponenten in diesem Vector sagt nichts über die Verbindung
     * der einzelnen Knoten!
     */
    private Vector<Node> nodes;

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.5130DA2D-1F3A-6294-28E5-F395FC81D8F9]
    // </editor-fold> 
    public Graph() {
        edges = new Vector<Edge>();
        nodes = new Vector<Node>();
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.3B67C02E-37A0-51DB-F372-E3C8C933731A]
    // </editor-fold>
    /**
     * Liefert alle Kanten des Graphen / Mi
     * liefer den Inhalt des edges-Vectors /Ma
     */
    public Vector<Edge> getEdges() {
        return edges;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.5F929CE3-B0F4-269A-FE8E-B5F05AE4B15F]
    // </editor-fold>
    /**
     * Setzte alle Kanten neu
     * setzt die neu berechneten Werte für die Vector-Elemente ein
     * @param edges
     */
    public void setEdges(Vector<Edge> edges) {
        this.edges = edges;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.6EDC3729-2E06-F791-43BF-F9BCE200E906]
    // </editor-fold>
    /**
     * Zeilen bzw. Spalten sind L1- und L2-Knoten
     * @return quadratische Gewichtsmatrix
     */
    public Matrix getMatrix() {
        return new Matrix(this).getSquareMatrix();//Matrix erzeugen und als Parameter den Graph an den Konstruktor übergeben
    }

    /**
     * Zeilen: n L1-Knoten; Spalten: m L2-Knoten
     * @return n x m -Gewichtsmatrix
     */
    public Matrix getWeightMatrix() {
        return new Matrix(this).getTransitionMatrix();
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.E9E4E316-A051-CCFE-EAA4-21BE94001B45]
    // </editor-fold>
    /**
     * wird nach Lernschritten aufgerufen
     * @param matrix
     */
    public void setMatrix(Matrix matrix) {
        HashMap<Node, Integer> nodeToIndexMapping = getMappingNodeToIndex();
        for (int i = 0; i < this.edges.size(); i++) {
            Edge edge = edges.elementAt(i);
            int indexOfFrom = nodeToIndexMapping.get(edge.getFrom());
            int indexOfTo = nodeToIndexMapping.get(edge.getTo());
            edge.setWeight(matrix.getValues()[indexOfFrom][indexOfTo]);
        }
    }

    /*
    public void setTransitionMatrix(Matrix matrix) {
    HashMap<Node, Integer> nodeToIndexMapping = getMappingNodeToIndex();
    for (int i = 0; i < this.edges.size(); i++) {
    Edge edge = edges.elementAt(i);
    int indexOfFrom = nodeToIndexMapping.get(edge.getFrom());
    int indexOfTo = nodeToIndexMapping.get(edge.getTo());
    edge.setWeight(matrix.getValues()[indexOfFrom][indexOfTo]);
    }
    }
     */
    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.709E6927-1792-C57E-FE46-65D65DA5DA4E]
    // </editor-fold>
    /**
     * liefert alle Knoten des Graphen als Vektor
     * @return Vektor mit den Knoten
     */
    public Vector<Node> getNodes() {
        return nodes;
    }

    public Node getNodeByName(String name){
        for (Node node : nodes){
            if (node.getName().equals(name)){
                return node;
            }
        }
        return null;
    }

    public Vector<Node> getLayer1Nodes() {
        Vector<Node> layer1Nodes = new Vector<Node>();
        for (Node node : nodes) {
            if (node instanceof Layer1Node) {
                layer1Nodes.add(node);
            }
        }
        return layer1Nodes;
    }

    public Vector<Node> getLayer2Nodes() {
        Vector<Node> layer2Nodes = new Vector<Node>();
        for (Node node : nodes) {
            if (node instanceof Layer2Node) {
                layer2Nodes.add(node);
            }
        }
        return layer2Nodes;
    }

    public HashMap<Node, Integer> getMappingNodeToIndex() {
        HashMap<Node, Integer> mapping = new HashMap<Node, Integer>();
        int i = 0;
        for (Node node : getLayer1Nodes()) {
            mapping.put(node, i);
            i++;
        }
        for (Node node : getLayer2Nodes()) {
            mapping.put(node, i);
            i++;
        }

        return mapping;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.7C3AF8D4-9E45-4E22-A942-9D704B8573A8]
    // </editor-fold>
    /**
     * setzte alle Knoten des Graphen neu
     * setzt die threshold-Werte einzelner Knoten als Vektorkomponenten
     * @param nodes Vector mit allen Knoten des Graphen als Inhalt
     */
    public void setNodes(Vector<Node> nodes) {
        this.nodes = nodes;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.0EDBCE53-860E-C1A7-9F09-E2424911A79E]
    // </editor-fold>
    /**
     * Fügt dem Graphen eine Kante hinzu
     * fügt dem Vector edges eine Komponente hinzu (kann am Ende sein)
     * @param edge Objekt der Klasse Edge
     */
    public void addEdge(Edge edge) {
        edges.add(edge);
        //muss an knoten das hinzufüge weitergeben
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.92506B57-D666-E563-381E-FD0A8047DE7F]
    // </editor-fold>
    /**
     * Fügt dem Graphen einen Knoten hinzu
     * in den Vektor nodes wird eine Komponente hinzugefügt
     * @param node Objekt der Klasse Node
     */
    public void addNode(Node node) {
        nodes.add(node);
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.D9A33CAB-BEF4-C4DE-AE80-0DC93ABB4E42]
    // </editor-fold>
    /**Lösche eine Kante
     * eine Kante wird ausgewählt und diese wird aus dem Vector edges entfernt
     * Die Veränderung wird in setEdges festgehalten
     * @param edge KAnte die entfernt wird
     */
    public void deleteEdge(Edge edge) {
        edges.remove(edge);
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.9DB578D5-864B-22CF-DCE4-8F2C3CFBC806]
    // </editor-fold>
    /**Lösche einen knoten
     * es wird die ausgewählte Komponente aus dem Vector nodes entfernt
     * die Veränderung wird in setNodes festgehalten
     * @param node der zu entfernende Knoten
     */
    public void deleteNode(Node node) {
        for (int i = 0; i < node.getEdges().size(); i++) {
            edges.remove(node.getEdges().elementAt(i));
        }
        nodes.remove(node);
    }

    /**
     * geht durch die L2-Knoten und sammelt die outputs ein
     */
    public Vector<Double> getOutputVector() {

        Vector<Double> gesamtAusgabe = new Vector<Double>();
        for (int i = 0; i < getLayer2Nodes().size(); i++) {
            gesamtAusgabe.add(getLayer2Nodes().get(i).getOutputValue());
            System.out.print(getLayer2Nodes().get(i).getOutputValue()+",");
        }
        System.out.println();
        return gesamtAusgabe;
    }

    /**
     * hier wird die Startbelegung gesetzt
     * soll den inputValue für einen Knoten besetzen
     * @param input
     */
    public void setInputVector(Vector<Double> input) throws DimensionException {
        eingabe = input;


        if (input.size() != getLayer1Nodes().size()) {
            throw new DimensionException("Die Dimension des Eingabevektors stimmt nicht mit der Anzahl der Knoten in Schicht 1 überein!");


        } else {
            for (int i = 0; i < getLayer1Nodes().size(); i++) {
                getLayer1Nodes().get(i).setInputValue(input.get(i));
            }
        }
    }

    public Vector<Double> getInputVector() {
        return eingabe;
    }

// Besetzt die Knoten der ersten Schicht mit Werten
    //soll: zwei Indizes haben(Eingabeknoten i, Nummer j)
    public void setInputVector(Vector<Double> input, int anzahl) {
        Vector<Integer> eingabeMenge = new Vector<Integer>(anzahl);
        Vector<Double> eingabeVektor = new Vector<Double>(getLayer1Nodes().size());

        for (int j = 0; j < anzahl; j++) {
            for (int i = 0; i < getLayer1Nodes().size(); i++) {
                //eingabeVektor.get(i)=input.get(i);//beim i-ten Knoten wird der Ausgangswert belegt
            }
            // eingabeMenge.get(j)=;
        }
    }

    /**
     * Gibt eine String-Repräsentation des Graphen in Mengenschreibweise (Menge der Kanten) aus.
     * @return String der Kantenmenge des Graphen
     */
    @Override
    public String toString() {
        StringBuffer str = new StringBuffer();

        if (!edges.isEmpty()) {
            str.append("{");

            for (int i = 0; i < edges.size(); i++) {
                Edge edge = edges.get(i);//Index im Vector, also eine Kante
                str.append("(" + edge.getFrom() + "," + edge.getTo() + "," + edge.getWeight() + "), ");
            }
            str.append("}");
        }

        return str.toString();


        /**
         * MEthode für die Belegung der Eingabevektoren
         * Vector aus Vectoren
         * setzt die Eingabev.
         * Die Eingabe kann als Eingabe in der Mainklasse simuliert werden
         *
         */
        /**
         * MEthode mit der die zu lernende Muster aus der Muster-Menge ausgewählt werden
         */
        /**
         * MEthode die die Reihenfolge der zu nehmenden Muster vorgibt
         */
    }

    public static Graph getExampleGraph() {
        Graph graph = new Graph();

        //  Erzeuge Nodes & Edges
        Node knoten1 = new Layer1Node("K 1");
        Node knoten2 = new Layer1Node("K 2");
        Node knoten5 = new Layer1Node("K 3");
        Node knoten3 = new Layer2Node("K 4");
        Node knoten4 = new Layer2Node("K 5");

        double gewicht = 1;
        Edge kante1zu3 = new Edge(knoten1, knoten3, gewicht);
        Edge kante1zu4 = new Edge(knoten1, knoten4, -2);
        Edge kante2zu4 = new Edge(knoten2, knoten4, -0.2);
        Edge kante5zu3 = new Edge(knoten5, knoten3, gewicht);
        Edge kante5zu4 = new Edge(knoten5, knoten4, -4);

        knoten1.setOutputValue(2);
        knoten2.setOutputValue(1);
        knoten5.setOutputValue(1);

        //  add edges to graph
        graph.addEdge(kante2zu4);
        graph.addEdge(kante1zu3);
        graph.addEdge(kante1zu4);
        graph.addEdge(kante5zu3);
        graph.addEdge(kante5zu4);

        graph.addNode(knoten1);
        graph.addNode(knoten2);
        graph.addNode(knoten3);
        graph.addNode(knoten4);
        graph.addNode(knoten5);

        knoten1.setOutputValue(1);
        knoten2.setOutputValue(1);
        knoten5.setOutputValue(1);


        return graph;
    }

    public IController getController() {
        return controller;
    }

    public void setController(IController controller) {
        this.controller = controller;
    }
}

